package com.wzapp.sitposture.authentication.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.wzapp.sitposture.authentication.pojo.view.WeXinSign;
import com.wzapp.sitposture.authentication.service.WeXinService;
import com.wzapp.sitposture.common.error.CustomizedException;
import com.wzapp.sitposture.config.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Formatter;
import java.util.UUID;

/**
 * 功能: 签名分析的网页url<br/>
 *
 * @author ql
 * @version 0.4
 * @create 2018-06-30 11:23
 **/
@Slf4j
@Service
public class WeXinServiceImpl implements WeXinService {

    public static final String WEXIN_SHARE_JS_TICKET = "wexinShareJsTicket";
    public static final String WEXIN_SHARE_ACCESS_TOKEN = "wexinShareAccessToken";

    @Autowired
    private RedisService redisService;

    @Autowired
    private Environment environment;

    /**
     * 功能: 签名分析的网页url<br/>
     *
     * @param url
     * @return com.eig.moment.entity.WeXinSign
     * @author ql
     * @create 2018-08-30 14:43
     **/
    @Override
    public WeXinSign signature(String url) throws CustomizedException {

        log.info("share url param:" + url);

        String jsapi_ticket = obtainJsTicket();

        WeXinSign weXinSign = sign(jsapi_ticket, url);
        weXinSign.setAppId(environment.getProperty("wexin.share.appid"));

        log.info(weXinSign.toString());
        return weXinSign;
    }

    /**
     * 功能: 获取jsTicket <br/>
     *
     * @param
     * @return java.lang.String
     * @author ql
     * @create 2018-08-30 14:43
     **/
    private String obtainJsTicket() throws CustomizedException {

        //从redis中获取jsticket
        Object jsTicketObj = redisService.get(WEXIN_SHARE_JS_TICKET);
        if (null != jsTicketObj) {
            return jsTicketObj.toString();
        }
        //获取微信access_token
        String accessToken = obtainWeXinAccessToken();

        String ticketUrl = environment.getProperty("wexin.share.auth.ticket.url");
        ticketUrl = ticketUrl.replace("?1", accessToken);

        JSONObject ticketResult = obtainSSLGet(ticketUrl);
        if (null==ticketResult||StringUtils.isEmpty(ticketResult.getString("ticket"))) {
            throw new CustomizedException("weixin.share.obtain.jsticket.error");
        }

        String jsTicket = ticketResult.getString("ticket");
        //设置jsticket 过期时间6000秒
        redisService.set(WEXIN_SHARE_JS_TICKET, jsTicket, 6000L);
        return jsTicket;
    }

    /**
     * 功能: 获取微信客户端access_token<br/>
     *
     * @param
     * @return java.lang.String
     * @author ql
     * @create 2018-08-30 14:43
     **/
    private String obtainWeXinAccessToken() throws CustomizedException {

        Object accessTokenObj = redisService.get(WEXIN_SHARE_ACCESS_TOKEN);
        if (null != accessTokenObj) {
            return accessTokenObj.toString();
        }

        String appid = environment.getProperty("wexin.share.appid");
        String secret = environment.getProperty("wexin.share.secret");
        String tokenUrl = environment.getProperty("wexin.share.auth.token.url");
        tokenUrl = tokenUrl.replace("?1", appid);
        tokenUrl = tokenUrl.replace("?2", secret);

        //正确：{"access_token":"ACCESS_TOKEN","expires_in":7200}
        //错误：{"errcode":40013,"errmsg":"invalid appid"}
        JSONObject tokenResult = obtainSSLGet(tokenUrl);
        if (null == tokenResult) {
            throw new CustomizedException("weixin.share.obtain.accesstoken.error");
        }
        //access_token 过期时间7200秒
        String accessToken = tokenResult.getString("access_token");
        if (accessToken == null) {
            log.error("微信获取token异常 accessToken=null,tokenResult:{}",tokenResult.toJSONString());
            throw new CustomizedException("weixin.share.obtain.accesstoken.error");
        }
        //设置过期时间6000秒
        redisService.set(WEXIN_SHARE_ACCESS_TOKEN, accessToken, 6000L);
        return accessToken;
    }

    /**
     * 功能: https Get请求<br/>
     *
     * @param url
     * @return com.alibaba.fastjson.JSONObject
     * @author ql
     * @create 2018-08-30 14:43
     **/
    private JSONObject obtainSSLGet(String url) {

        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
            URL console = new URL(url);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.setRequestMethod("GET");
            conn.setRequestProperty("content-type", "text/json");
            conn.setRequestProperty("Proxy-Connection", "Keep-Alive");
            conn.setDoInput(true);
            conn.setDoOutput(true);
            BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream());
            hurlBufOus.flush();
            hurlBufOus.close();
            InputStream is = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            String line = null;
            StringBuilder sb = new StringBuilder();

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

            JSONObject accessToken = JSONObject.parseObject(sb.toString());
            return accessToken;
        } catch (Exception e) {
            log.error("obtainSSLGet,message:{},stack:",e.getMessage(), e);
        }finally {

        }
        return null;
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        private TrustAnyHostnameVerifier() {
        }

        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private static class TrustAnyTrustManager implements X509TrustManager {
        private TrustAnyTrustManager() {
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    public static WeXinSign sign(String jsapi_ticket, String url) {
        WeXinSign weXinSign = new WeXinSign();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String signature = "";

        //注意这里参数名必须全部小写，且必须有序
        String string1 = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            log.error("WeXinSign.sign NoSuchAlgorithmException：message:{},stack:",e.getMessage(), e);
        } catch (UnsupportedEncodingException e) {
           log.error("WeXinSign.sign UnsupportedEncodingException：message:{},stack:",e.getMessage(), e);
        }

        weXinSign.setUrl(url);
        weXinSign.setNonceStr(nonce_str);
        weXinSign.setTimestamp(timestamp);
        weXinSign.setSignature(signature);
        return weXinSign;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}
